﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;

namespace Lbb.Data
{
    /// <summary>
    /// 排序器.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// Author      Chiron
    /// Created     2011-5-28
    /// LastUpdated 2011-5-28
    public class Ordering<T> : IOrdering where T : class
    {
        /// <summary>
        /// 数据库对象.
        /// </summary>
        public DataContext DB { get; set; }

        /// <summary>
        /// 当前表名.
        /// </summary>
        public string TableName { get; private set; }

        /// <summary>
        /// 主键字段.
        /// </summary>
        public string PrimaryKeyField { get; private set; }

        /// <summary>
        /// 排序字段.
        /// </summary>
        public string OrderingField { get; private set; }

        /// <summary>
        /// 父级字段.
        /// </summary>
        public string ParentField { get; private set; }

        /// <summary>
        /// 有父级.
        /// </summary>
        public bool HasParent { get; private set; }

        /// <summary>
        /// 当前操作的表.
        /// </summary>
        public Table<T> Table { get; private set; }

        /// <summary>
        /// 实体公有属性集.
        /// </summary>
        public readonly static PropertyInfo[] ENTITY_PROPS = typeof(T).GetProperties();

        /// <summary>
        /// 构造一个默认的排序器.
        /// </summary>
        /// <param name="cdc">数据库对象</param>
        public Ordering(DataContext cdc)
        {
            Init(cdc, "Ordering", "ParentID");
        }

        /// <summary>
        /// 制定一个排序器.
        /// </summary>
        /// <param name="db">数据库对象.</param>
        /// <param name="orderingField">排序字段.</param>
        public Ordering(DataContext db, string orderingField, string parentField)
        {
            Init(db, orderingField, parentField);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="db"></param>
        /// <param name="orderingField"></param>
        /// <param name="parentField"></param>
        private void Init(DataContext db, string orderingField, string parentField)
        {
            var t = typeof(T);
            var attr = Attribute.GetCustomAttributes(t.Assembly).SingleOrDefault(a => a.GetType() == typeof(TableAttribute)) as TableAttribute;
            TableName = attr != null ? attr.Name : t.Name;

            Table = db.GetTable<T>();
            if (Table == null) throw new Exception(string.Format("不存在指定表 {0}.", TableName));
            DB = db;

            OrderingField = orderingField;
            ParentField = parentField;
            foreach (var prop in ENTITY_PROPS)
            {
                var keyAttr = Attribute.GetCustomAttributes(prop).SingleOrDefault(a => a.GetType() == typeof(ColumnAttribute)) as ColumnAttribute;
                if (keyAttr != null && keyAttr.IsPrimaryKey)
                {
                    PrimaryKeyField = string.IsNullOrEmpty(keyAttr.Name) ? prop.Name : keyAttr.Name;
                }
            }

            if (string.IsNullOrEmpty(PrimaryKeyField)) throw new Exception(string.Format("没有找到表{0}主键字段!", TableName));

            var parent = ENTITY_PROPS.Where(p => p.Name.ToLower() == ParentField.ToLower());
            HasParent = parent != null && parent.Count() > 0;
        }

        /// <summary>
        /// 刷新排序的Sql语句.表名:{0},主键字段名:{1},排序字段名:{2},条件语句:{3}
        /// </summary>
        protected readonly static string refreshOrderingSql =
                                                                @"SELECT ROW_NUMBER() OVER (ORDER BY {2} ASC) AS RowNumber, {1}
                                                                INTO #Temp
                                                                FROM {0}
                                                                {3}
                                                                Order By {2}

                                                                UPDATE {0}
                                                                SET {2} = TM.RowNumber
                                                                FROM {0} M, #Temp TM
                                                                WHERE M.{1} = TM.{1}
                                                                DROP TABLE #Temp";
        /// <summary>
        /// 刷新排序
        /// </summary>
        /// <param name="id">记录标识.</param>
        protected virtual void RefreshOrdering(object id)
        {
            string param = string.Empty;
            dynamic pid = null;
            if (HasParent)
            {
                var getPidSql = string.Format("SELECT {0} FROM {1} WHERE [{2}]", ParentField, TableName, PrimaryKeyField) + "={0}";
                pid = DB.ExecuteQuery(ENTITY_PROPS.Single(p => p.Name == ParentField).PropertyType, getPidSql, id).Cast<dynamic>().FirstOrDefault();
                param = string.Format("WHERE [{0}]", ParentField) +
                    (pid == null ? " IS NULL" : "={0}");
            }
            var sql = string.Format(refreshOrderingSql, TableName, PrimaryKeyField, OrderingField, param);
            if (pid == null)
            {
                DB.ExecuteCommand(sql);
            }
            else
            {
                DB.ExecuteCommand(sql, pid);
            }
        }

        /// <summary>
        /// 移动记录.
        /// </summary>
        /// <param name="id">记录标识.</param>
        /// <param name="isUp">是否为向上移动.</param>
        public virtual void Move(object id, bool isUp)
        {
            RefreshOrdering(id);
            var sql = "SELECT * FROM {0} WHERE {1}";
            //取得当前记录.
            var record = DB.ExecuteQuery<T>(string.Format(sql, TableName, "[" + PrimaryKeyField + "]={0}"), id).FirstOrDefault();
            if (record == null) throw new Exception(string.Format("无法完成操作,因为不存在主键为{0}的记录.", id));

            var isNull = " IS NULL";
            object pid = null;
            var param = string.Empty;//条件.
            var upSql = "UPDATE {0} SET {1}";
            var setReplaceSql = string.Format(upSql, TableName, "[" + OrderingField + "]={0}");//更新替换记录的Sql语句.
            var setCurrSql = setReplaceSql;
            var getMaxSql = string.Format("SELECT MAX({0}) FROM {1}", OrderingField, TableName);//取得最大排序号的Sql语句.
            if (HasParent)
            {
                //取得父级ID
                var pidProp = ENTITY_PROPS.Single(p => p.Name == ParentField);
                pid = pidProp.GetValue(record, null);

                param = string.Format(" WHERE [{0}]", ParentField);//+pid==null?" IS NULL":"={0}";
                setReplaceSql += param + (pid == null ? isNull + " AND [" + OrderingField + "]={1}" : "={1} AND [" + OrderingField + "]={2}");
                getMaxSql += param + (pid == null ? isNull : "={0}");
            }
            else
            {
                setReplaceSql += " WHERE [" + OrderingField + "]={1}";
            }

            //取得当前父级下的最大排序号
            int? max;
            if (pid != null)
            {
                max = DB.ExecuteQuery<int?>(getMaxSql, pid).FirstOrDefault();
            }
            else
            {
                max = DB.ExecuteQuery<int?>(getMaxSql).FirstOrDefault();
            }
            if (!max.HasValue) max = 1;
            //取得旧排序号.
            var orderingProp = ENTITY_PROPS.Single(p => p.Name == OrderingField);
            int oldOrdering = (int)orderingProp.GetValue(record, null);
            //新排序号
            int newOrdering = oldOrdering + (isUp ? -1 : +1);
            if (newOrdering < 1 || newOrdering > max.Value) return;//已在最前或最后就不调整.
            //更新替换记录的排序.
            if (pid != null)
            {
                DB.ExecuteCommand(setReplaceSql, oldOrdering, pid, newOrdering);
            }
            else
            {
                DB.ExecuteCommand(setReplaceSql, oldOrdering, newOrdering);
            }
            //更新当前记录的排序.
            DB.ExecuteCommand(setCurrSql + " WHERE [" + PrimaryKeyField + "]={1}", newOrdering, id);
        }

        /// <summary>
        /// 取得下一个排序序号.
        /// </summary>
        /// <param name="paramx">条件.</param>
        /// <returns></returns>
        /// <example>GetNextOrdering(new{ParentID=null})</example>
        public virtual int GetNextOrdering(object paramx)
        {
            var props = paramx.GetType().GetProperties();
            var param = new Dictionary<string, object>();
            foreach (var prop in props)
            {
                param.Add(prop.Name, prop.GetValue(paramx, null));
            }
            var sql = string.Format("SELECT [{0}] FROM {1}", OrderingField, typeof(T).Name);

            if (param.Count > 0)
            {
                int i = 0;
                sql += " WHERE " +
                    param.Select(p =>
                        string.Format("[{0}]", p.Key) + (p.Value != null ? ("={" + i++.ToString() + "}") : " IS NULL")
                    ).Aggregate((d, e) => d + " AND " + e);
            }
            var data = DB.ExecuteQuery<int>(sql, param.Where(p => p.Value != null).Select(p => p.Value).ToArray()).ToArray();
            if (data.Count() == 0) return 1;
            return data.Max() + 1;
        }
    }
}
